﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommandLine;
using CommandLine.Text;
using SharpSvn;

namespace SvnFlow
{
    public class Program
    {
        static void Main(string[] args)
        {
            var options = new CmdOptions();
            if (!new CommandLineParser().ParseArguments(args, options) || options.ShowHelp)
            {
                ShowHelp(options);
                return;
            }
            if (!String.IsNullOrEmpty(options.GeneratePathList))
            {
                options.GeneratePath = options.GeneratePathList.Split(new[] {','});
                if (options.GeneratePath.Count() != 2)
                {
                    Logger.WriteLine(@"Invalid argument for option ""generate""");
                    ShowHelp(options);
                    return;
                }
                if(!Directory.Exists(options.GeneratePath[0]))
                {
                    Logger.WriteLine("Path {0} not exists.", options.GeneratePath[0]);
                    return;
                }
                if (!Directory.Exists(options.GeneratePath[1]))
                {
                    Logger.WriteLine("Path {0} not exists.", options.GeneratePath[1]);
                    return;
                }
            }

            try
            {
                var branchesFilePath = GetBranchesFilePath(options, GetProgramDirectory());

                if (options.CreateNewConfig || options.GeneratePath.Count() == 2)
                {
                    if (File.Exists(branchesFilePath))
                    {
                        Logger.WriteLine("File {0} already exists.", branchesFilePath);
                        return;
                    }

                    BranchesFile.CreateEmpty(branchesFilePath, DateTime.Now, options);
                }
                else
                {
                    if (!File.Exists(branchesFilePath))
                    {
                        Logger.WriteLine("File {0} dasn't exists.", branchesFilePath);
                        Logger.WriteLine("Are you wish to create an empty configuration file (Y/N)?");
                        if (Console.ReadKey(true).Key == ConsoleKey.Y)
                        {
                            BranchesFile.CreateEmpty(branchesFilePath, DateTime.Now, options);
                        }
                        ShowHelp(options);
                        return;
                    }
                    MergeBranches(options, branchesFilePath);
                }
            }
            catch (Exception ex)
            {
                Logger.WriteLine("Exception: " + ex);
            }

            WriteMessageAndWait(options.PauseOn, "Finished!");
        }

        internal static void MergeBranches(CmdOptions options, string branchesFilePath)
        {
            var branchesFile = new BranchesFile(branchesFilePath);

            var fromBranch = branchesFile.CreateBranchInfo("left");
            var toBranch = branchesFile.CreateBranchInfo("right");

            if (fromBranch == null || toBranch == null)
                return;

            if (fromBranch.Uri == toBranch.Uri)
            {
                Logger.WriteLine("Branches can't use the same URLs");
                return;
            }

            if (options.BackDirection)
            {
                Swap(ref fromBranch, ref toBranch);
            }

            var flowSvnClient = new FlowSvnClient(CreateSvnClient());

            WriteMessageAndWait(options.PauseOn,
                                "Switch to " + toBranch.Name.ToUpper() + " network environment...");
            flowSvnClient.PrepareWorkingCopy(toBranch);
            toBranch.WorkingCopyRevision = flowSvnClient.GetWorkingCopyRevision(toBranch);

            WriteMessageAndWait(options.PauseOn,
                                "Switch to " + fromBranch.Name.ToUpper() + " network environment...");
            flowSvnClient.PrepareWorkingCopy(fromBranch);
            fromBranch.WorkingCopyRevision = flowSvnClient.GetWorkingCopyRevision(fromBranch);

            var range = fromBranch.Range;
            if (range.StartRevision == range.EndRevision)
            {
                Logger.WriteLine("Nothing to merge");
                return;
            }

            var mappedFolders = new List<FolderMap>();
            if (branchesFile.MappedFolders.Count == 0)
            {
                mappedFolders.Add(new FolderMap { Left = string.Empty, Right = string.Empty });
            }
            else
            {
                mappedFolders.AddRange(branchesFile.MappedFolders
                        .Select(map => options.BackDirection
                            ? new FolderMap { Left = map.Right, Right = map.Left }
                            : new FolderMap { Left = map.Left, Right = map.Right }));
            }

            var logMessages = new List<SvnLogMessage>();
            foreach (var folders in mappedFolders)
            {
                logMessages.AddRange(flowSvnClient.BuildLogMessage(fromBranch, range, folders.Left));
            }
            var orderedLog = from log in logMessages.Distinct() orderby log.Revision select log;
            StoreLogMessage(Path.GetDirectoryName(branchesFilePath), orderedLog.Distinct());

            foreach (var folders in mappedFolders)
            {
                flowSvnClient.MergeChanges(fromBranch, folders.Left, toBranch, folders.Right, range);
                flowSvnClient.FixSpecialProperties(toBranch, folders.Right, branchesFile.MergeSvnProperties);
                flowSvnClient.MarkPropertyConflictsResolved(toBranch, folders.Right);
            }

            branchesFile.AddHistoryItem(DateTime.Now, fromBranch.Name, toBranch.Name, range);
            branchesFile.Save();
        }

        private static SvnClient CreateSvnClient()
        {
            var svnClient = new SvnClient();

            svnClient.Progress +=
                (o, eventArgs) =>
                Logger.Write("{0} / {1}                                  \r",
                              eventArgs.Progress.ToString("#,###"), eventArgs.TotalProgress.ToString("#,###"));

            svnClient.SvnError +=
                (o, eventArgs) => Logger.WriteLine("Svn Error: " + eventArgs.Exception.Message);

            return svnClient;
        }

        private static void StoreLogMessage(string logPath, IEnumerable<SvnLogMessage> logMessages)
        {
            var logFileName = Path.Combine(logPath, "log.txt");
            var log = string.Join(Environment.NewLine, logMessages.Select(x => x.ToString()).ToArray());
            File.WriteAllText(logFileName, log);
            Logger.WriteLine("Log was saved to: {0}", logFileName);
        }

        private static string GetBranchesFilePath(CmdOptions options, string programPath)
        {
            return string.IsNullOrEmpty(options.ConfFileName)
                ? Path.Combine(programPath, "Branches.xml") : Path.GetFullPath(options.ConfFileName);
        }

        private static void ShowHelp(CmdOptions options)
        {
#if DEBUG
            var help = new HelpText("SvnFlow Application (DEBUG)");
#else
            var help = new HelpText("SvnFlow Application" +
                             String.Format(" v{0} ({1}) {2}", Version.VersionNum, Version.RevisionNum,
                                           Version.RevisionDate));
#endif
            help.AddOptions(options);
            Logger.WriteLine(help.ToString());
        }

        private static void WriteMessageAndWait(bool pause, string text)
        {
            Logger.WriteLine(text);

            if (pause) Console.ReadLine();
        }

        private static string GetProgramDirectory()
        {
            return Path.GetDirectoryName(typeof(Program).Assembly.Location);
        }

        private static void Swap(ref BranchInfo from, ref BranchInfo to)
        {
            var temp = from;
            from = to;
            to = temp;
        }
    }
}
